﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using Tesla.Math;

namespace Tesla.Graphics.Implementation {
    /// <summary>
    /// Abstract class for concrete subclasses to provide the underlying implementation of <see cref="BlendState"/>.
    /// </summary>
    public abstract class BlendStateImplementation : GraphicsResourceImplementation {
        private bool _isBound;
        private BlendFunction _alphaBlendFunction;
        private Blend _alphaSrcBlend;
        private Blend _alphaDestBlend;
        private BlendFunction _colorBlendFunction;
        private Blend _colorSrcBlend;
        private Blend _colorDestBlend;
        private Color _blendFactor;
        private int _multiSampleMask;
        private bool[] _blendEnable;
        private ColorWriteChannels[] _writeChannels;

        /// <summary>
        /// Gets or protected sets if this BlendStateImplementation has been bound to the pipeline and its underlying objects created.
        /// </summary>
        public bool IsBound {
            get {
                return _isBound;
            }
            protected set {
                _isBound = value;
            }
        }

        /// <summary>
        /// Gets or sets the alpha blend function.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public BlendFunction AlphaBlendFunction {
            get {
                return _alphaBlendFunction;
            }
            set {
                ThrowIfBound();
                _alphaBlendFunction = value;
            }
        }

        /// <summary>
        /// Gets or sets the alpha source blend.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public Blend AlphaSourceBlend {
            get {
                return _alphaSrcBlend;
            }
            set {
                ThrowIfBound();
                _alphaSrcBlend = value;
            }
        }

        /// <summary>
        /// Gets or sets the alpha destination blend.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public Blend AlphaDestinationBlend {
            get {
                return _alphaDestBlend;
            }
            set {
                ThrowIfBound();
                _alphaDestBlend = value;
            }
        }

        /// <summary>
        /// Gets or sets the color blend function.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public BlendFunction ColorBlendFunction {
            get {
                return _colorBlendFunction;
            }
            set {
                ThrowIfBound();
                _colorBlendFunction = value;
            }
        }

        /// <summary>
        /// Gets or sets the color source blend.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public Blend ColorSourceBlend {
            get {
                return _colorSrcBlend;
            }
            set {
                ThrowIfBound();
                _colorSrcBlend = value;
            }
        }

        /// <summary>
        /// Gets or sets the color destination blend.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public Blend ColorDestinationBlend {
            get {
                return _colorDestBlend;
            }
            set {
                ThrowIfBound();
                _colorDestBlend = value;
            }
        }

        /// <summary>
        /// Gets or sets the blend factor color.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public Color BlendFactor {
            get {
                return _blendFactor;
            }
            set {
                ThrowIfBound();
                _blendFactor = value;
            }
        }

        /// <summary>
        /// Gets or sets the multi sample mask.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public int MultiSampleMask {
            get {
                return _multiSampleMask;
            }
            set {
                ThrowIfBound();
                _multiSampleMask = value;
            }
        }

        /// <summary>
        /// Gets the number of render targets that blending can be enabled for.
        /// </summary>
        public abstract int BlendEnableCount {
            get;
        }

        /// <summary>
        /// Gets the number of render targets that can have an independent write mask for.
        /// </summary>
        public abstract int ColorWriteChannelsCount {
            get;
        }

        /// <summary>
        /// Creates a new instance of <see cref="BlendStateImplementation"/>.
        /// </summary>
        protected BlendStateImplementation() {
            _writeChannels = new ColorWriteChannels[this.ColorWriteChannelsCount];
            _blendEnable = new bool[this.BlendEnableCount];
            _isBound = false;
        }

        /// <summary>
        /// Gets if blending is enabled for the render target index.
        /// </summary>
        /// <param name="index">Render target index</param>
        /// <returns>True if enabled, false otherwise</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is less than zero or greater than <see cref="BlendEnableCount"/></exception>
        public bool GetBlendEnable(int index) {
            if(index < 0 || index > _blendEnable.Length) {
                throw new ArgumentOutOfRangeException("Index is out of range");
            }
            return _blendEnable[index];
        }

        /// <summary>
        /// Sets if blending is enabled for the render target index.
        /// </summary>
        /// <param name="index">Render target index</param>
        /// <param name="enable">True if enabled, false if not</param>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is less than zero or greater than <see cref="BlendEnableCount"/></exception>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public void SetBlendEnable(int index, bool enable) {
            ThrowIfBound();

            if(index < 0 || index > _blendEnable.Length) {
                throw new ArgumentOutOfRangeException("Index is out of range");
            }
            _blendEnable[index] = enable;
        }

        /// <summary>
        /// Gets the write mask used by the render target specified by its index.
        /// </summary>
        /// <param name="index">Render target index</param>
        /// <returns>Write mask</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is less than zero or greater than <see cref="ColorWriteChannelsCount"/></exception>
        public ColorWriteChannels GetWriteChannels(int index) {
            if(index < 0 || index > _writeChannels.Length) {
                throw new ArgumentOutOfRangeException("Index is out of range");
            }
            return _writeChannels[index];
        }

        /// <summary>
        /// Sets the write mask used by the render target specified by its index.
        /// </summary>
        /// <param name="index">Render target index</param>
        /// <param name="mask">Color write mask to use</param>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is less than zero or greater than <see cref="ColorWriteChannelsCount"/></exception>
        /// <exception cref="System.InvalidOperationException">Thrown if the implementation is bound. Once bound render states are read-only.</exception>
        public void SetWriteChannels(int index, ColorWriteChannels mask) {
            ThrowIfBound();

            if(index < 0 || index > _writeChannels.Length) {
                throw new ArgumentOutOfRangeException("Index is out of range");
            }
            _writeChannels[index] = mask;
        }

        /// <summary>
        /// Binds the implementation. This is called the first time an unbound state is set to the device or manually by the user in order to
        /// create the underlying state ahead of time (best practice). Once called the state properties are read-only.
        /// </summary>
        public abstract void BindBlendState();

        private void ThrowIfBound() {
            if(_isBound) {
                throw new InvalidOperationException("Cannot change render state once it has been bound to the pipeline.");
            }
        }
    }
}
